package com.belf.framework.security.handle;

import com.alibaba.fastjson2.JSON;
import com.belf.common.constant.HttpStatus;
import com.belf.common.core.domain.AjaxResult;
import com.belf.common.utils.ServletUtils;
import com.belf.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.*;
import org.springframework.security.web.util.RedirectUrlBuilder;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * 认证失败处理类 返回未授权
 *
 * @author admin
 */
@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = -8970718410437077606L;

    /**
     * 向调用者提供有关哪些HTTP端口与系统上的哪些HTTPS端口相关联的信息
     */
    private PortMapper portMapper = new PortMapperImpl();
    /**
     * 端口解析器，基于请求解析出端口
     */
    private PortResolver portResolver = new PortResolverImpl();
    /**
     * 登陆页面URL
     */
    private String loginFormUrl;
    /**
     * 默认为false，即不强制Https转发或重定向
     */
    private boolean forceHttps = false;
    /**
     * 重定向策略
     */
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public String getLoginFormUrl() {
        return loginFormUrl;
    }

    public void setLoginFormUrl(String loginFormUrl) {
        this.loginFormUrl = loginFormUrl;
    }

    private static final String DEFAULT_INDEX_URL = "/index";


    /**
     * 允许子类修改成适用于给定请求的登录表单URL
     */
    protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
        return getLoginFormUrl();
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String requestURI = request.getRequestURI();
        String userAgent = request.getHeader("User-Agent");
        log.info(userAgent);
        //这里需要兼容App，当App访问时需要返回json格式数据，而不是跳转到首页
        if (!DEFAULT_INDEX_URL.equals(requestURI)) {
            int code = HttpStatus.UNAUTHORIZED;
            String msg = StringUtils.format("请求访问：{}，认证失败，无法访问系统资源", request.getRequestURI());
            ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
            return;
        }
        String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
        redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

    /**
     * 构建重定向URL
     *
     * @param request
     * @param response
     * @param authException
     * @return
     */
    protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        // 通过determineUrlToUseForThisRequest方法获取URL
        String loginForm = determineUrlToUseForThisRequest(request, response, authException);
        // 如果是绝对URL，直接返回
        if (UrlUtils.isAbsoluteUrl(loginForm)) {
            return loginForm;
        }
        // 如果是相对URL
        // 构造重定向URL
        int serverPort = portResolver.getServerPort(request);
        String scheme = request.getScheme();

        RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();

        urlBuilder.setScheme(scheme);
        urlBuilder.setServerName(request.getServerName());
        urlBuilder.setPort(serverPort);
        urlBuilder.setContextPath(request.getContextPath());
        urlBuilder.setPathInfo(loginForm);

        if (forceHttps ) {
            Integer httpsPort = portMapper.lookupHttpsPort(serverPort);

            if (httpsPort != null) {
                // 覆盖重定向URL中的scheme和port
                urlBuilder.setScheme("https");
                urlBuilder.setPort(httpsPort);
            } else {
                log.warn("Unable to redirect to HTTPS as no port mapping found for HTTP port " + serverPort);
            }
        }

        return urlBuilder.getUrl();
    }

    /**
     * 设置为true以强制通过https访问登录表单
     * 如果此值为true（默认为false），并且触发拦截器的请求还不是https
     * 则客户端将首先重定向到https URL，即使serverSideRedirect（服务器端转发）设置为true
     */
    public void setForceHttps(boolean forceHttps) {
        this.forceHttps = forceHttps;
    }
}
